Skip to content

feat(vault): gate the secret store on a deployer input#183

Open
ItamarZand88 wants to merge 12 commits into
mainfrom
feat/enabled-vault
Open

feat(vault): gate the secret store on a deployer input#183
ItamarZand88 wants to merge 12 commits into
mainfrom
feat/enabled-vault

Conversation

@ItamarZand88

@ItamarZand88 ItamarZand88 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Gate a Vault (secret store) on a boolean deployer input — the same
.enabled(input) gate the other stores already have — across AWS
CloudFormation and AWS/GCP/Azure Terraform. Includes a small fix so a
declined resource doesn't leave a stale grant behind in the permission
profiles.

When a stack renders, for each gated resource:

  1. The setup emitter stamps the gate onto the resource and every IAM grant
    that targets it — a Terraform count / CloudFormation Condition for
    stores that own a cloud block; the vault is a Parameter Store / Secret
    Manager name prefix with no block, so its gate rides the IAM grant blocks
    and the registration entry instead.
  2. (the heart) When the deployer answers no, the resource and its grants
    drop out: no cloud resource, no registration entry, and — the small fix —
    no leftover resource-scoped grant in any permission profile, so nothing
    re-applies that grant at runtime as a project-level binding.
  3. Preflight rejects the ways a prefix gate could still leak: a *-family
    grant over a gated secret namespace, or a vault/postgres nested inside a
    sibling's name prefix.

What I did

  • Vault gating for AWS-CFN and AWS/GCP/Azure-TF. AWS/GCP vaults have no
    cloud block (they're a name prefix), so the gate rides the IAM grant blocks
    plus the dropped registration entry rather than a resource count; Azure's
    Key Vault is a real block, gated by count with every reference indexed.
  • Small fix: strip_declined_resources now also drops a declined
    resource's resource-scoped entry from every permission profile — and from an
    Extend/Override management profile — not just the resource and its
    registration entry. Otherwise a runtime consumer (the GCP service-account
    controller, or the management role) reads that entry straight from the
    profile and re-grants it after handoff.
  • Preflight rules for secret namespaces: reject a family wildcard grant
    over a gated vault/postgres, and reject a gated resource nested in a
    sibling's name prefix (where a prefix wildcard would still cover it).
  • Folded the queue generator tests into a shared queue+vault file (they share
    the gating shape); all prior queue coverage is reproduced.

Files touched

  • crates/alien-terraform/src/emitters/{aws,gcp,azure}/vault.rs,
    crates/alien-cloudformation/src/emitters/aws/vault.rs — vault gating.
  • crates/alien-deployment/src/pending.rs — the grant-drop fix.
  • crates/alien-preflights/src/compile_time/resource_enabled_valid.rs
    secret-namespace preflight rules.
  • Generator tests + snapshots (CFN + TF enabled_vault_queue_tests).

How I tested

  • cargo nextest run -p alien-deployment — 89 pass, including the strip tests
    that assert a declined resource's grant is gone from a named profile (while a
    kept sibling's grant and the "*" wildcard survive — the removal is
    exact-key, not a prefix sweep) and from an Extend/Override management profile.
  • cargo nextest run -p alien-cloudformation enabled_vault_queue (9) and
    -p alien-terraform enabled_vault_queue (15, incl. terraform validate) —
    both green. These resolve the whole template with the gate off and assert no
    grant survives over the vault's namespace, so the check is blind to which
    emitter produced a grant.
  • cargo nextest run -p alien-preflights secret-namespace rules — green.
  • Real-cloud e2e (terraform_aws_push_enabled_demo, wired into
    e2e-cloud.yml behind an enabled-demo app filter): a demo app with a
    matched on/off pair of every gated type provisioned to AWS via Terraform. It
    asserts each *-on resource and its grant reach the imported stack_state and
    the account, and each *-off is absent. Passed end to end (provision →
    assert → destroy); cleanup verified.

Security review (this PR touches IAM grants and secret access):

  • A deployer disables a store but its grant lingers and gets re-applied at
    runtime → fails: the grant leaves every profile, so the service-account
    controller has nothing to re-apply (strip test + the emitter-agnostic
    "no ungated grant over the namespace" property tests).
  • A declined vault app still reads a live sibling app-config through the
    <prefix>-app-* wildcard → fails: preflight rejects a gated resource nested
    in a sibling's name prefix, and rejects a *-family grant over the gated
    namespace.
  • A declined resource keeps a management-plane grant → fails:
    strip_declined_resources drops the declined id from an Extend/Override
    management profile (test a_declined_resource_loses_its_management_grant),
    the emitters gate the per-resource management grants, and the generator
    hard-errors if a gated emitter doesn't opt into gating.
  • Nothing turned up.

Extends enablement to Vault across AWS, GCP and Azure Terraform plus
CloudFormation. On AWS and GCP the vault owns no block at all — those stores are
name prefixes rather than resources — so its IAM grants are the only thing it
can gate, and they had been left standing.

That is not cosmetic. The grant is a data-plane read (ssm:GetParameter,
roles/secretmanager.secretAccessor) over a name-prefix wildcard, and resource ids
allow interior hyphens, so vaults `app` and `app-config` coexist happily.
Declining `app` while leaving `<prefix>-app-*` granted leaves the runtime role
able to read `app-config`'s secrets. Saying no has to withdraw the permission,
not just the registration entry.

On GCP the fix needed a second emitter. The service account's profile loop emits
a byte-identical copy of the same grant, which the generator had been collapsing
by body-identity dedup. Gating the vault alone made the two bodies differ, dedup
stopped firing, and two ungated copies survived — holding exactly the access the
deployer had declined. So the service account emitter is gated too, via
gate_bindings. The shared project-wide custom roles are deliberately excluded:
they already carry their own var.gcp_manage_custom_roles count, a second count is
a hard conflict, and tying them to one vault's gate would delete roles that
vault's siblings still hold.

Azure is the one cloud where the vault is a real resource, and every role
assignment names it twice — once as `scope`, once inside the uuidv5 seed that
derives the assignment's name. The seed is a raw interpolation, so it is the
reference most likely to be left behind.

The tests assert the property rather than the mechanism. Checking that the
vault's own policy carries the gate is the assertion that missed the duplicate
grant: it passes straight over an ungated twin. So they resolve the template with
the gate off and ask what access is still standing.
…mespace

Vault and postgres data grants are name-prefix scoped, so a grant on 'app'
covers every secret under 'app-*', including a sibling 'app-config'. Declining
'app-config' withdraws its own grants but not the sibling's, leaving the
declined namespace readable and writable. Refuse the gate unless the sibling
follows the same input, or the ids are renamed apart.
The fixture gated 'secrets', the reserved deployment secrets vault the
preflight refuses. Rename it to the id the grant-scoped tests already use.
…space

A '*'-scoped vault or postgres data grant binds by secret-name prefix over
the whole stack, whichever type wrote the secret, so on GCP a postgres grant
keeps reading a declined vault's namespace. The wildcard rule now flags either
family member's grant when the gated resource is in the family.
strip_declined_resources removed a deployer-declined gated resource from
the desired stack but left its resource-scoped entry in every permission
profile. On GCP the service-account controller derives project-level
Vertex bindings straight from the profile (IAM can't scope to a
sub-resource), so a declined resource's grant was still applied at deploy
time — a runtime fail-open of the .enabled() gate. Prune the entry from
every profile so no runtime consumer re-grants it; the "*" wildcard,
which is not resource-scoped, stays.
The strip test only proved the "*" wildcard survived; add a kept sibling
resource with its own resource-scoped grant and assert it stays, so the
test proves the removal is exact-key and never sweeps a sibling.

Also trim the vault supports_enabled_when docstrings and the queue/vault
test docs to lead with the prefix-wildcard footgun instead of restating
that the vault owns no block, and point the strip comment at the preflight
that makes exact-key removal safe.
@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown

Greptile Summary

Adds deployer-input gating for Vault resources and their IAM grants across CloudFormation and Terraform.

  • Gates AWS and GCP prefix-based vault grants and Azure Key Vault resources and role assignments.
  • Removes declined resources from named permission profiles during deployment preparation.
  • Adds secret-namespace preflight validation and expanded generator coverage.

Confidence Score: 2/5

This PR should not merge until management-profile grants are stripped with declined resources and the new namespace checks are scoped to platforms where the overlap exists.

Initial setup can preserve a declined resource's management grant, while the platform-independent preflight logic also blocks valid AWS and Azure stack configurations.

crates/alien-deployment/src/pending.rs; crates/alien-preflights/src/compile_time/resource_enabled_valid.rs

Security Review

The named-profile grant cleanup does not include the separate management profile, allowing a resource-scoped management grant to survive after a gated resource is declined.

Important Files Changed

Filename Overview
crates/alien-deployment/src/pending.rs Removes declined-resource grants from named profiles but omits the separately stored management profile.
crates/alien-preflights/src/compile_time/resource_enabled_valid.rs Adds useful namespace protections, but applies GCP-specific vault/postgres overlap rules to AWS and Azure.
crates/alien-terraform/src/emitters/aws/vault.rs Gates emitted AWS vault IAM policy blocks while preserving prefix-based import and binding data.
crates/alien-terraform/src/emitters/gcp/vault.rs Gates GCP vault IAM bindings without incorrectly gating shared custom roles.
crates/alien-terraform/src/emitters/azure/vault.rs Counts Azure Key Vault and role-assignment blocks and indexes reachable resource references.
crates/alien-cloudformation/src/emitters/aws/vault.rs Opts the prefix-only AWS vault emitter into generator-applied CloudFormation conditions.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Input[Deployer gate input] --> Render[CFN / Terraform gating]
  Input --> Strip[Strip declined resource]
  Render --> Cloud[Cloud resource and IAM grants]
  Strip --> Named[Named permission profiles]
  Strip -. omitted .-> Mgmt[Management permission profile]
  Mgmt --> Runtime[Runtime permission consumers]
  Runtime --> IAM[Management IAM grant]
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
crates/alien-deployment/src/pending.rs:169-171
**Management grants survive decline**

When an initial setup declines a gated resource with a resource-scoped management grant, this loop cleans only named profiles and leaves the separate management profile unchanged, causing downstream emitters and permission controllers to retain management access to the declined resource's namespace.

### Issue 2 of 2
crates/alien-preflights/src/compile_time/resource_enabled_valid.rs:112-124
**Namespace family ignores platform**

When an AWS or Azure stack combines a gated vault with a postgres wildcard grant or prefix-nested sibling, this platform-independent family check rejects it even though the cross-type namespace overlap exists only on GCP, blocking valid AWS and Azure manifests before rendering.

Reviews (1): Last reviewed commit: "test(deployment): assert kept grants sur..." | Re-trigger Greptile

Comment on lines +169 to +171
for profile in stack.permissions.profiles.values_mut() {
profile.0.shift_remove(resource_id.as_str());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 security Management grants survive decline

When an initial setup declines a gated resource with a resource-scoped management grant, this loop cleans only named profiles and leaves the separate management profile unchanged, causing downstream emitters and permission controllers to retain management access to the declined resource's namespace.

Knowledge Base Used: Deployment Engine (alien-deployment)

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/alien-deployment/src/pending.rs
Line: 169-171

Comment:
**Management grants survive decline**

When an initial setup declines a gated resource with a resource-scoped management grant, this loop cleans only named profiles and leaves the separate management profile unchanged, causing downstream emitters and permission controllers to retain management access to the declined resource's namespace.

**Knowledge Base Used:** [Deployment Engine (alien-deployment)](https://app.greptile.com/alien/-/custom-context/knowledge-base/alienplatform/alien/-/docs/deployment-engine.md)

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +112 to +124
let flagged_prefixes: Vec<String> =
if NAME_PREFIX_GRANTED_TYPES.contains(&resource_type.as_ref()) {
NAME_PREFIX_GRANTED_TYPES
.iter()
.map(|family_type| {
format!(
"{}/",
crate::mutations::management_permission_profile::permission_resource_type(
family_type,
)
)
})
.collect()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Namespace family ignores platform

When an AWS or Azure stack combines a gated vault with a postgres wildcard grant or prefix-nested sibling, this platform-independent family check rejects it even though the cross-type namespace overlap exists only on GCP, blocking valid AWS and Azure manifests before rendering.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/alien-preflights/src/compile_time/resource_enabled_valid.rs
Line: 112-124

Comment:
**Namespace family ignores platform**

When an AWS or Azure stack combines a gated vault with a postgres wildcard grant or prefix-nested sibling, this platform-independent family check rejects it even though the cross-type namespace overlap exists only on GCP, blocking valid AWS and Azure manifests before rendering.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional. The check is deliberately platform-independent: a stack is portable across clouds and resource ids cannot be renamed after deploy, so a config whose vault/postgres namespaces would overlap on GCP is rejected everywhere rather than allowed to reach a GCP deploy where it leaks.

The declined-resource strip cleaned every named permission profile but not
the separate management profile. An Extend/Override management profile is a
second resource-scoped grant store the management role reads from, so a
declined resource's entry there kept the management role a namespace grant
the resource no longer backs. Strip it from the management profile too;
Auto needs nothing, it is re-derived from the stripped resource set.
Adds a distribution test app with a worker plus a matched on/off pair of
every gated resource type (KV, storage, queue, vault) behind
.enabled(input). The test answers the four *-on inputs true and the four
*-off inputs false, provisions to AWS via Terraform, then asserts each
enabled resource and its grant reach the imported stack_state and the
account while each declined resource is absent. Wires TestApp::EnabledDemo
through the harness (input_values -> tfvars) and adds the terraform-aws
matrix entry to e2e-cloud.yml behind an enabled-demo app filter.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant